using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using Fiddler;
using mshtml;

namespace DeploymentWang.Fiddler
{
    public class SessionState
    {
        public bool CaptureEnabled { get; set; }
        public string SessionId { get; set; }

        public SessionState()
        {
            CaptureEnabled = false;
            SessionId = string.Empty;
        }
    }

    public class TestInstance
    {
        public DateTime CapturedOn { get; set; }
        public string Url { get; set; }
        public bool TimedOut { get; set; }
        public bool PurgeOutput { get; set; }
        public List<string> CapturedLinks;

        public TestInstance()
        {
            CapturedLinks = new List<string>();
        }
    }

    public class LinkCheckerExtension : IHandleExecAction, IAutoTamper
    {
        private SessionState mySessionState;
        private TestInstance myTestInstance;

        public bool OnExecAction(string sCommand)
        {
            var returnCode = true;

            try
            {
                Trace.WriteLine("DeploymentWang.LinkCheckerExtension starting...");

                var cmdElements = Utilities.Parameterize(sCommand);

                Trace.WriteLine(string.Format("Launched with {0} args", cmdElements.Length));

                if (cmdElements.Length != 4)
                    return false;
                if (string.Compare(cmdElements[0], "linkcheck", StringComparison.OrdinalIgnoreCase) != 0)
                    return false;

                var url = cmdElements[1];
                var outputFilename = cmdElements[2];
                var sessionId = cmdElements[3];

                InitialiseCapture(url, sessionId);

                Trace.WriteLine(string.Format("[{0}] Looking for broken links on '{1}'",
                    Thread.CurrentThread.ManagedThreadId,
                    url));

                var dlg = new Action<string>(InvokeUrl);
                var result = dlg.BeginInvoke(url, null, null);
                result.AsyncWaitHandle.WaitOne();
                dlg.EndInvoke(result);

                DumpCapture(myTestInstance, outputFilename);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("********* DeploymentWang encountered an exception *********");
                Trace.WriteLine(ex.Message);
                returnCode = false;
            }
            finally
            {
                SuspendCapture();
            }

            return returnCode;
        }

        private void InitialiseCapture(string url, string sessionId)
        {
            FiddlerApplication.UI.actRemoveAllSessions();

            myTestInstance = new TestInstance
                           {
                               CapturedOn = DateTime.Now,
                               Url = url
                           };

            if (string.CompareOrdinal(sessionId, mySessionState.SessionId) != 0)
            {
                myTestInstance.PurgeOutput = true;
                mySessionState.SessionId = sessionId;
            }
        }

        private void ResumeCapture()
        {
            mySessionState.CaptureEnabled = true;
        }

        private void SuspendCapture()
        {
            mySessionState.CaptureEnabled = false;
        }

        private void DumpCapture(TestInstance test, string outputFile)
        {
            Trace.WriteLine(string.Format("Found {0} broken links", test.CapturedLinks.Count));

            if (test.PurgeOutput)
                File.Delete(outputFile);
            if (test.CapturedLinks.Count == 0)
                return;

            Trace.WriteLine(string.Format("Saving to '{0}'", outputFile));
            Directory.CreateDirectory(Path.GetDirectoryName(outputFile));

            using (var sw = new StreamWriter(outputFile, true, Encoding.UTF8))
            {
                if (test.TimedOut)
                    sw.WriteLine("{1}{0}{2}{0}{3}{0}404{4}",
                        "\t",
                        test.CapturedOn,
                        test.Url,
                        string.Empty,
                        "TimedOut");

                foreach (var capturedLink in test.CapturedLinks)
                {
                    sw.WriteLine("{1}{0}{2}{0}{3}{0}{4}", 
                        "\t",
                        test.CapturedOn, 
                        test.Url,
                        capturedLink,
                        "404");
                    Trace.WriteLine(capturedLink);
                }
            }
        }

        private void InvokeUrl(string url)
        {
            try
            {
                var doc = new HTMLDocument();
                var doc2 = (IHTMLDocument2)doc;
                var doc4 = (IHTMLDocument4)doc;

                // This is the key ingredient - have to put some HTML
                // in the DOM before using it, even though we're not
                // accessing the DOM.
                doc2.write("<html></html>");
                doc2.close();

                ResumeCapture();

                IHTMLDocument2 iDoc2 = doc4.createDocumentFromUrl(url, "");

                var sw = new Stopwatch();
                sw.Start();

                // run until the document loads or 60s approx timeout
                while (true)
                {
                    if (iDoc2.readyState == "complete")
                        break;
                    if (iDoc2.readyState == "loaded")
                        break;
                    if (sw.ElapsedMilliseconds <= 60000)
                    {
                        Thread.Sleep(500);
                        continue;
                    }

                    myTestInstance.TimedOut = true;
                    break;
                }

                sw.Stop();
            }
            finally
            {
                SuspendCapture();
            }
        }

        public void OnLoad()
        {
            mySessionState = new SessionState();
        }

        public void OnBeforeUnload()
        {

        }



        public void AutoTamperRequestBefore(Session oSession)
        {
            
        }

        public void AutoTamperRequestAfter(Session oSession)
        {
            
        }

        public void AutoTamperResponseBefore(Session oSession)
        {
            
        }

        public void AutoTamperResponseAfter(Session oSession)
        {
            if (!mySessionState.CaptureEnabled)
                return;
            if (oSession.responseCode != 404)
                return;

            myTestInstance.CapturedLinks.Add(oSession.fullUrl);
        }

        public void OnBeforeReturningError(Session oSession)
        {
            Trace.WriteLine(string.Format("AutoTamper [{0}]: encountered session error", 
                Thread.CurrentThread.ManagedThreadId));
        }
    }
}